#include "SDLWindow.h"
#include <SDL.h>
#include <SDL_opengl.h>
#include <stdexcept>
#include <SEWindow/SDLWindow/SDLEventFactory.h>
#include <SERender/Drive/Drive.h>

#include <SEDebug/Log/Log.h>
#include <SECore/Events/Input.h>

namespace SEngine
{
    struct SDLInitHelper 
    {
        SDLInitHelper() 
        {
            SDL_Init(SDL_INIT_VIDEO);
            SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
            SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
            SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);

            SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
            SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
            SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);

            SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
            SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 8);

            SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1); 


            SDL_GL_SetSwapInterval(1); // Enable vsync
        }
        virtual ~SDLInitHelper() 
        {
            SDL_GL_DeleteContext(SDLWindow::g_glContext);
            SDL_Quit();
        }
    };

    SDLWindow::SDLWindow(const std::string & title, int w, int h, uint32_t flags)
    {
        m_sdlInitHelper = std::make_unique<SDLInitHelper>();
        m_window = SDL_CreateWindow(title.c_str(),SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, w, h, flags);
        if (!m_window) {
            throw std::runtime_error("SDL_CreateWindow Error!");
        }
        if (flags & SDLWindowFlags::SE_WINDOW_OPENGL) {
            if (!g_glContext) {
                g_glContext = SDL_GL_CreateContext(m_window);
                if (CurrentDrive().Load(SDL_GL_GetProcAddress) != 0) {
                    Log::GetInstance().Error("Opengl Load error!");
                }
            }
        }

        // CurrentDrive().SetEnable(FRAMEBUFFER_SRGB);

        Input::GetInstance().SignalGetMousePos.AddSlot([](float& x, float& y) {
            int x_, y_;
            SDL_GetMouseState(&x_, &y_);
            x = (float)x_; y = (float)y_;
            return SlotState::Keep;
        });

        Input::GetInstance().SignalSetRelativeMouseMode.AddSlot([](bool v) {
            SDL_SetRelativeMouseMode((SDL_bool)v);
            return SlotState::Keep;
        });

        Input::GetInstance().SignalShowCursor.AddSlot([](bool v) {
            SDL_ShowCursor(v ? SDL_ENABLE : SDL_DISABLE);
            return SlotState::Keep;
        });

        Input::GetInstance().SignalGetMouseRelativeMouseState.AddSlot([](float& dx, float& dy) {
            int dx_ = 0, dy_ = 0;
            SDL_GetRelativeMouseState(&dx_, &dy_);
            dx = (float)dx_; dy = (float)dy_;
            return SlotState::Keep;
        });

        Input::GetInstance().SignalWarpMouseInWindow.AddSlot([this](float x, float y) {
            SDL_WarpMouseInWindow(m_window, (int)x, (int)y);
            return SlotState::Keep;
        });
    }
    
    SDLWindow::~SDLWindow()
    {
        if (m_window) {
            SDL_DestroyWindow(m_window);
        }        
    }

    void SDLWindow::PollEvent(EventHandler &&fn)
    {
        SDL_Event event;
        while(SDL_PollEvent(&event)) {
            eventSignal.Invoke(event);
            SDLEventFactory factory(event);
            if (auto e = factory.Create(); e) {
                fn(e);
            }
        }
    }

    void SDLWindow::SwapBuffer()
    {
        SDL_GL_SwapWindow(m_window);
    }

    std::pair<int, int> SDLWindow::GetWindowSize()
    {
        int w{0}, h{0};
        SDL_GetWindowSize(m_window, &w, &h);
        return {w, h};
    }

} // namespace SEngine
